#!/usr/bin/env python

__author__ = "Abhinav Sarkar"
__version__ = "0.1"
__license__ = "GNU Lesser General Public License"


class Webservice (object):

	DEFAULT_CACHE_TIMEOUT = 3600 # cache for 1 minute

	def __init__(self,
				 input_encoding=None,
				 request_headers=None):
		self._cache = FileCache()
		self._urllib = urllib2
		self._cache_timeout = Webservice.DEFAULT_CACHE_TIMEOUT
		self._InitializeRequestHeaders(request_headers)
		self._InitializeUserAgent()
		self._input_encoding = input_encoding

	def GetUser(self, username):
		url = "http://ws.audioscrobbler.com/1.0/user/%s/profile.xml" % username
		try:
			xml = self._FetchUrl(url)
		except urllib2.HTTPError,e:
			if e.code == 404:
				raise AudioscrobblerError("User does not exist")
		data = Xml2Dict(ElementTree.XML(xml))
		user = User()

		for k,v in User.attributeMap.items():
			try:
				user.__setattr__(k, v[1](eval('data' + v[0])))
			except KeyError:
				pass
		return user

	def GetArtist(self, name):
		artist = Artist(name = name)
		try:
			artist.GetSimilarArtists()
		except urllib2.HTTPError,e:
			if e.code == 404:
				raise AudioscrobblerError("Artist does not exist")
		return artist
		
	def GetAlbum(self, name, artist):
		if type(artist) == Artist:
			artist = artist.name
		url = "http://ws.audioscrobbler.com/1.0/album/%s/%s/info.xml" %\
				(artist, name)
		try:
			xml = self._FetchUrl(url)
		except urllib2.HTTPError,e:
			if e.code == 404:
				raise AudioscrobblerError("Album does not exist:")
		data = Xml2Dict(ElementTree.XML(xml))
		
		album = Album(
					name = data['@title'],
					coverart = data['coverart'],
					artist = Artist(name = data['@artist']),
					url = data['url']
				)
				
		if 'mbid' in data:
			album.mbid = data['mbid']
			
		if 'releasedate' in data:
			album.releaseDate = datetime(*(time.strptime(data['releasedate'],
									'%d %b %Y, 00:00')[0:6]))
		album.reach = data['reach']
		
		album.tracks = []
		if not 'track' in data['tracks']:
			return album

		for trackData in data['tracks']['track']:
			track = Track(
				artist = album.artist,
				name = trackData['@title'],
				url = trackData['url']
			)

			track.reach = int(trackData['reach'])
			album.tracks.append(track)
		return album
		
	def GetTrack(self, name, artist):
		if type(artist) == Artist:
			artist = artist.name
		track = Track(name = name, artist = Artist(name = artist))
		try:
			track.GetSimilarTracks()
		except urllib2.HTTPError,e:
			if e.code == 404:
				raise AudioscrobblerError("Track does not exist")
		return track
	
	def GetTag(self, name):
		tag = Tag(name = name)
		try:
			tag.GetTopArtists()
		except urllib2.HTTPError,e:
			if e.code == 404:
				raise AudioscrobblerError("Tag does not exist")
		return tag
		
	def GetOverallTopTags(self):
		url = "http://ws.audioscrobbler.com/1.0/tag/toptags.xml"
		xml = self._FetchUrl(url)
		data = Xml2Dict(ElementTree.XML(xml))
		
		return [Tag(
			name = tagData['@name'],
			count = tagData['@count'],
			url = tagData['@url']
		) for tagData in data['tag']]
		
	def GetCountry(self, name):
		return Country(name = name)
		
	def GetGroup(self, name):
		group = Group(name = name)
		try:
			group.GetWeeklyArtistChart()
		except urllib2.HTTPError,e:
			if e.code == 404:
				raise AudioscrobblerError("Group does not exist")
		return group
		
	def SetCache(self, cache):
		'''Override the default cache.  Set to None to prevent caching.

		Args:
			cache: an instance that supports the same API as the audioscrobblerws.FileCache
		'''
		self._cache = cache

	def SetUrllib(self, urllib):
		'''Override the default urllib implementation.

		Args:
			urllib: an instance that supports the same API as the urllib2 module
		'''
		self._urllib = urllib

	def SetCacheTimeout(self, cache_timeout):
		'''Override the default cache timeout.

		Args:
			cache_timeout: time, in seconds, that responses should be reused.
		'''
		self._cache_timeout = cache_timeout

	def SetUserAgent(self, user_agent):
		'''Override the default user agent

		Args:
		user_agent: a string that should be send to the server as the User-agent
		'''
		self._request_headers['User-Agent'] = user_agent

	def _BuildUrl(self, url, path_elements=None, extra_params=None):
		# Break url into consituent parts
		(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
		path = path.replace(' ', '+')

		# Add any additional path elements to the path
		if path_elements:
			# Filter out the path elements that have a value of None
			p = [i for i in path_elements if i]
			if not path.endswith('/'):
				path += '/'
				path += '/'.join(p)

		# Add any additional query parameters to the query string
		if extra_params and len(extra_params) > 0:
			extra_query = self._EncodeParameters(extra_params)
			# Add it to the existing query
			if query:
				query += '&' + extra_query
			else:
				query = extra_query

		# Return the rebuilt URL
		return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))

	def _InitializeRequestHeaders(self, request_headers):
		if request_headers:
			self._request_headers = request_headers
		else:
			self._request_headers = {}

	def _InitializeUserAgent(self):
		user_agent = 'Python-urllib/%s (python-Webservice/%s)' % \
					 (self._urllib.__version__, __version__)
		self.SetUserAgent(user_agent)

	def _GetOpener(self, url):
		opener = self._urllib.build_opener()
		opener.addheaders = self._request_headers.items()
		return opener

	def _Encode(self, s):
		if self._input_encoding:
			return unicode(s, self._input_encoding).encode('utf-8')
		else:
			return unicode(s).encode('utf-8')

	def _EncodeParameters(self, parameters):
		'''Return a string in key=value&key=value form

			Values of None are not included in the output string.

			Args:
				parameters:
					A dict of (key, value) tuples, where value is encoded as
					specified by self._encoding
			Returns:
				A URL-encoded string in "key=value&key=value" form
		'''
		if parameters is None:
			return None
		else:
			return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))


	def _FetchUrl(self,
				  url,
				  parameters = None,
				  no_cache = False):
		'''Fetch a URL, optionally caching for a specified time.

		Args:
			url: The URL to retrieve
			parameters: A dict of key/value pairs that should added to
						the query string. [OPTIONAL]
			no_cache: If true, overrides the cache on the current request

		Returns:
			A string containing the body of the response.
		'''
		# Add key/value parameters to the query string of the url
		url = self._BuildUrl(url, extra_params=parameters)

		# Get a url opener that can handle basic auth
		opener = self._GetOpener(url)

		# Open and return the URL immediately if we're not going to cache
		if no_cache or not self._cache or not self._cache_timeout:
			url_data = opener.open(url).read()
		else:
			# Unique keys are a combination of the url and the username
			key = url.encode('utf-8')

			# See if it has been cached before
			last_cached = self._cache.GetCachedTime(key)

			# If the cached version is outdated then fetch another and store it
			if not last_cached or time.time() >= last_cached + self._cache_timeout:
				url_data = opener.open(url).read()
				self._cache.Set(key, url_data)
			else:
				url_data = self._cache.Get(key)

		# Always return the latest version
		return url_data
		
import urllib
import urllib2
import urlparse
import cElementTree as ElementTree
import time
from datetime import datetime

from album import Album
from artist import Artist
from country import Country
from event import Event
from filecache import FileCache
from group import Group
from tag import Tag
from track import Track
from user import User
from xml2dict import Xml2Dict
from error import AudioscrobblerError